package wechat

import (
	"context"
	"errors"
	"fmt"
	"github.com/wechatpay-apiv3/wechatpay-go/core"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
	"github.com/wechatpay-apiv3/wechatpay-go/services/payments/native"
	"time"
	"webook/payment/domain"
	"webook/payment/events"
	"webook/payment/repository"
	"webook/pkg/logger"
)

var errUnknownTransactionState = errors.New("未知的微信事务状态")

type NativePaymentService struct {
	appID     string
	mchID     string
	notifyURL string                       // 支付通知回调 URL
	repo      repository.PaymentRepository // 本地维护的支付记录

	svc      *native.NativeApiService
	producer events.Producer

	l logger.LoggerV1

	// 在微信 native 里面，分别是
	// SUCCESS：支付成功
	// REFUND：转入退款
	// NOTPAY：未支付
	// CLOSED：已关闭
	// REVOKED：已撤销（付款码支付）
	// USERPAYING：用户支付中（付款码支付）
	// PAYERROR：支付失败(其他原因，如银行返回失败)
	nativeCBTypeToStatus map[string]domain.PaymentStatus
}

func NewNativePaymentService(appID string, mchID string, repo repository.PaymentRepository,
	svc *native.NativeApiService, producer events.Producer, l logger.LoggerV1) *NativePaymentService {
	return &NativePaymentService{appID: appID, mchID: mchID, notifyURL: "http://test.com/pay/callback",
		repo: repo, svc: svc, l: l,
		nativeCBTypeToStatus: map[string]domain.PaymentStatus{
			"SUCCESS":  domain.PaymentStatusSuccess,
			"PAYERROR": domain.PaymentStatusFailed,
			"NOTPAY":   domain.PaymentStatusInit,
			"CLOSED":   domain.PaymentStatusFailed,
			"REVOKED":  domain.PaymentStatusFailed,
			"REFUND":   domain.PaymentStatusRefund,
		},
		producer: producer,
	}
}

// 预支付 给予一些信息 返回codeUrl 在前端可以被转化为微信支付的二维码
func (n *NativePaymentService) Prepay(ctx context.Context, pmt domain.Payment) (string, error) {
	pmt.Status = domain.PaymentStatusInit
	err := n.repo.AddPayment(ctx, pmt)
	if err != nil {
		return "", err
	}
	resp, _, err := n.svc.Prepay(ctx, native.PrepayRequest{
		Appid:       core.String(n.appID),
		Mchid:       core.String(n.mchID),
		Description: core.String(pmt.Description),
		OutTradeNo:  core.String(pmt.BizTradeNO),
		TimeExpire:  core.Time(time.Now().Add(time.Minute * 30)), // 过期时间
		Amount: &native.Amount{
			Total:    core.Int64(pmt.Amt.Total),
			Currency: core.String(pmt.Amt.Currency),
		},
	})

	if err != nil {
		return "", err
	}
	return *resp.CodeUrl, nil
}

// 对账
func (n *NativePaymentService) SyncWechatInfo(ctx context.Context, bizTradeNO string) error {
	//根据订单号和商品号主动查询订单状态
	txn, _, err := n.svc.QueryOrderByOutTradeNo(ctx, native.QueryOrderByOutTradeNoRequest{
		OutTradeNo: core.String(bizTradeNO),
		Mchid:      core.String(n.mchID),
	})
	if err != nil {
		return err
	}
	return n.updateByTxn(ctx, txn) //更新本地数据库
}

// 查找过期的订单 查询自己的数据库
func (n *NativePaymentService) FindExpiredPayment(ctx context.Context, offset, limit int, t time.Time) ([]domain.Payment, error) {
	return n.repo.FindExpiredPayment(ctx, offset, limit, t)
}

// 根据业务id获取订单 查询自己的数据库
func (n *NativePaymentService) GetPayment(ctx context.Context, bizTradeId string) (domain.Payment, error) {
	return n.repo.GetPayment(ctx, bizTradeId)
}

// 回调接口 更新数据状态
func (n *NativePaymentService) HandleCallback(ctx context.Context, txn *payments.Transaction) error {
	return n.updateByTxn(ctx, txn)
}

// 更新状态到本地数据库里 并且通知相关业务方
func (n *NativePaymentService) updateByTxn(ctx context.Context, txn *payments.Transaction) error {
	status, ok := n.nativeCBTypeToStatus[*txn.TradeState]
	if !ok {
		return fmt.Errorf("%w, 微信的状态是 %s", errUnknownTransactionState, *txn.TradeState)
	}
	err := n.repo.UpdatePayment(ctx, domain.Payment{ //更新本地数据库里的状态
		TxnID:      *txn.TransactionId, // 微信过来的 transaction id
		BizTradeNO: *txn.OutTradeNo,
		Status:     status,
	})
	if err != nil {
		return err
	}
	//通知业务方：有的系统会根据支付状态决定是否要通知；站在业务的角度 至少应该发成功一次
	err1 := n.producer.ProducePaymentEvent(ctx, events.PaymentEvent{
		BizTradeNO: *txn.OutTradeNo,
		Status:     status.AsUint8(),
	})
	if err1 != nil {
		n.l.Error("发送支付事件失败", logger.Error(err), logger.String("biz_trade_no", *txn.OutTradeNo))
		//job16 失败的话存储到数据库里 开定时任务定时发送失败的事件
		go func() {
			ctx1, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			err := n.repo.AddLocalEvent(ctx1, domain.LocalEvent{
				BizTradeNO: *txn.OutTradeNo, BizStatus: status,
			})
			if err != nil {
				n.l.Error("存储失败的支付事件 失败", logger.Error(err), logger.String("biz_trade_no", *txn.OutTradeNo))
				//这里失败的话考虑发送告警 人工处理，或是增加重试机制
			}
		}()
	}
	return nil
}

// 查询本地消息表里待处理的消息
func (n *NativePaymentService) FindInitLocalEvents(ctx context.Context, offset, limit int) ([]domain.LocalEvent, error) {
	return n.repo.FindInitLocalEvent(ctx, offset, limit)
}

// 更新本地消息表
func (n *NativePaymentService) UpdateLocalEvent(ctx context.Context, bizTradeNO string, flag bool) error {
	status := domain.LocalEventStatusSuccess
	if !flag {
		status = domain.LocalEventStatusFailed
	}
	return n.repo.UpdateLocalEvent(ctx, domain.LocalEvent{
		BizTradeNO:  bizTradeNO,
		EventStatus: domain.LocalEventStatus(status),
	})
}
